package com.sun.service.service.impl;

import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.sun.common.core.constant.CodeEnum;
import com.sun.common.core.utils.DateUtils;
import com.sun.redis.config.annotation.DistributedRedisLock;
import com.sun.service.dao.TmSysNumResourceDao;
import com.sun.service.entity.TmSysNumResourceDo;
import com.sun.service.service.TmSysNumResourceService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.beans.Customizer;
import java.util.Date;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;


@Slf4j
@Service
public class TmSysNumResourceServiceImpl extends ServiceImpl<TmSysNumResourceDao, TmSysNumResourceDo> implements TmSysNumResourceService {

    @Resource
    private RedissonClient redissonClient;

    @Resource
    private TmSysNumResourceDao tmSysNumResourceDao;

    @Resource
    private DataSourceTransactionManager dataSourceTransactionManager;

    @Override
    public Boolean generateNumLock(String key, int length, Function<Long, Boolean> function) {
        log.info(Thread.currentThread().getName() + " :开启事务");
        DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
        definition.setPropagationBehavior(3);

        TransactionStatus transaction = dataSourceTransactionManager.getTransaction(definition);
        RLock lock = redissonClient.getLock("num");
        try {
            boolean tryLock = lock.tryLock();
            if (tryLock) {
                log.info(Thread.currentThread().getName() + " :加锁成功");
                // 读取流水表
                TmSysNumResourceDo tmSysNumResourceDo = tmSysNumResourceDao.selectOne(Wrappers.lambdaQuery(TmSysNumResourceDo.class).eq(TmSysNumResourceDo::getNumResource, key));
                // 这里的number 就是要给不同的业务去用这个值去更新他们的数据

                if (Objects.isNull(tmSysNumResourceDo)) {
                    log.error(key + ": 流水号不存在");
                    return false;
                }

                long number = Optional.ofNullable(tmSysNumResourceDo).map(TmSysNumResourceDo::getNextNo).orElseThrow(() -> new Exception("获取资源对象对应: " + key + "的流水号失败"));

                // number生成
                doAddNumber(tmSysNumResourceDo, length);

                // 将重新生成的number更新到流水表中
                int i = tmSysNumResourceDao.updateById(tmSysNumResourceDo);
                if (i == 0) {
                    // 这里的方法我要求绝对不能进来 ,否则我要求当前事务回滚
                    log.error("流水表更新失败..." + tmSysNumResourceDo.getVersion() + ":" + number);
                    throw new Exception("流水表更新失败");
                }

                // 这里就是根据 就是我接下来想实现的 利用java8的函数方法方式解决来调用不同业务的更新方法
                if (function.apply(number)) {
                    log.info(Thread.currentThread().getName() + " :目标方法执行成功,提交事务");
                    dataSourceTransactionManager.commit(transaction);
                    return true;
                } else {
                    // 事务回滚
                    log.error(Thread.currentThread().getName() + " :目标方法执行失败,整体事务回滚");
                    dataSourceTransactionManager.rollback(transaction);
                    return false;
                }

            } else {
                // 并发情况下 就一定会走到这里,这里只能让他等待
                log.error(Thread.currentThread().getName() + " :加锁失败,进行重试");
                return false;
            }

        } catch (Exception e) {
            log.error("原子性业务操作失败: " + e.getMessage());
            log.error(Thread.currentThread().getName() + " :原子性业务操作失败,整体事务回滚");
            dataSourceTransactionManager.rollback(transaction);
            return false;
        } finally {
            if (lock.isHeldByCurrentThread()) {
                try {
                    lock.unlock();
                    log.info(Thread.currentThread().getName() + " :解锁成功");
                } catch (Exception e) {
                    log.error(Thread.currentThread().getName() + " :解锁失败"); // 事务在锁释放之前释放 就一定不会出现这个问题 代码得做好 锁和事务两者释放的前后顺序
                }
            } else {
                log.error(Thread.currentThread().getName() + " :当前线程未持有锁");
            }
        }
    }

    public void doAddNumber(TmSysNumResourceDo tmSysNumResourceDo, int length) throws Exception {
        long serialNum = tmSysNumResourceDo.getNextNo();
        Date resetDate = tmSysNumResourceDo.getResetDate();
        long minVal = tmSysNumResourceDo.getMinVal();
        long maxVal = tmSysNumResourceDo.getMaxVal();
        int step = tmSysNumResourceDo.getStep();
        String resetType = tmSysNumResourceDo.getResetType();

        // 判断是否需要重置流水号
        boolean resetFlag = false;
        Date now = new Date();
        Date nowDay = DateUtils.formatToDay(now);
        Date resetDay = DateUtils.formatToDay(resetDate);

        if ("0".equals(resetType)) {
            if (nowDay.compareTo(resetDay) == 0) {
                // 日期相等，不需要重置
                resetFlag = false;
            } else if (nowDay.compareTo(resetDay) == 1) {
                // 执行日期 > 重置日期，需要重置
                resetFlag = true;
            } else if (nowDay.compareTo(resetDay) == -1) {
                // 执行日期 < 重置日期
                // 防止分布式多节点的时间戳不一致，给出5min偏差校验，如果当天的日期还不相等，则提醒
                Date newTime = new Date(now.getTime() + 5 * 60 * 1000);
                Date newTimeDay = DateUtils.formatToDay(newTime);

                if (newTimeDay.compareTo(resetDay) != 0) {
                    throw new Exception("无法获取早于重置时间的流水号");
                }

                resetFlag = false;
            }
        }

        if (resetFlag) {
            tmSysNumResourceDo.setNextNo(minVal + step).setResetDate(nowDay);
            serialNum = minVal;
        } else {
            tmSysNumResourceDo.setNextNo(serialNum + step);
        }

        // 如果流水号长度超出，则异常提醒
        String serialNumStr = String.valueOf(serialNum);
        if (serialNumStr.length() > length) {
            throw new Exception("获取流水号的长度超出");
        }

        // 如果(serialNum+step) >= maxVal，则流水号发行达到上限
        if ((serialNum + step) >= maxVal) {
            throw new Exception("获取流水号的达到上限");
        }

        tmSysNumResourceDo.setGmtModified(new Date());


        for (int i = serialNumStr.length(); i < length; i++) {
            //长度不够前面补0
            serialNumStr = 0 + serialNumStr;
        }

        tmSysNumResourceDo.setNumber(Long.valueOf(serialNumStr));
    }
}